
#include "WINGsP.h"
#include "wconfig.h"

#include <X11/keysym.h>
#include <X11/Xatom.h>

#include <ctype.h>

#define CURSOR_BLINK_ON_DELAY	600
#define CURSOR_BLINK_OFF_DELAY	300

const char *WMTextDidChangeNotification = "WMTextDidChangeNotification";
const char *WMTextDidBeginEditingNotification = "WMTextDidBeginEditingNotification";
const char *WMTextDidEndEditingNotification = "WMTextDidEndEditingNotification";

typedef struct W_TextField {
	W_Class widgetClass;
	W_View *view;

#if 0
	struct W_TextField *nextField;	/* next textfield in the chain */
	struct W_TextField *prevField;
#endif

	char *text;
	int textLen;		/* size of text */
	int bufferSize;		/* memory allocated for text */

	int viewPosition;	/* position of text being shown */

	int cursorPosition;	/* position of the insertion cursor */

	short usableWidth;
	short offsetWidth;	/* offset of text from border */

	WMRange selection;

	WMFont *font;

	WMTextFieldDelegate *delegate;

	WMHandlerID timerID;	/* for cursor blinking */

	struct {
		WMAlignment alignment:2;

		unsigned int bordered:1;

		unsigned int beveled:1;

		unsigned int enabled:1;

		unsigned int focused:1;

		unsigned int cursorOn:1;

		unsigned int secure:1;	/* password entry style */

		unsigned int pointerGrabbed:1;

		unsigned int ownsSelection:1;

		unsigned int waitingSelection:1;	/* requested selection, but
							 * didnt get yet */

		unsigned int notIllegalMovement:1;
	} flags;
} TextField;

#define NOTIFY(T,C,N,A) { WMNotification *notif = WMCreateNotification(N,T,A);\
    if ((T)->delegate && (T)->delegate->C)\
    (*(T)->delegate->C)((T)->delegate,notif);\
    WMPostNotification(notif);\
    WMReleaseNotification(notif);}

#define MIN_TEXT_BUFFER		2
#define TEXT_BUFFER_INCR	8

#define WM_EMACSKEYMASK   ControlMask

#define WM_EMACSKEY_LEFT  XK_b
#define WM_EMACSKEY_RIGHT XK_f
#define WM_EMACSKEY_HOME  XK_a
#define WM_EMACSKEY_END   XK_e
#define WM_EMACSKEY_BS    XK_h
#define WM_EMACSKEY_DEL   XK_d

#define DEFAULT_WIDTH		60
#define DEFAULT_HEIGHT		20
#define DEFAULT_BORDERED	True
#define DEFAULT_ALIGNMENT	WALeft

static void destroyTextField(TextField * tPtr);
static void paintTextField(TextField * tPtr);

static void handleEvents(XEvent * event, void *data);
static void handleTextFieldActionEvents(XEvent * event, void *data);
static void didResizeTextField(W_ViewDelegate * self, WMView * view);

struct W_ViewDelegate _TextFieldViewDelegate = {
	NULL,
	NULL,
	didResizeTextField,
	NULL,
	NULL
};

static void lostSelection(WMView * view, Atom selection, void *cdata);

static WMData *requestHandler(WMView * view, Atom selection, Atom target, void *cdata, Atom * type);

static WMSelectionProcs selectionHandler = {
	requestHandler,
	lostSelection,
	NULL
};

#define TEXT_WIDTH(tPtr, start) (WMWidthOfString((tPtr)->font, \
    &((tPtr)->text[(start)]), (tPtr)->textLen - (start)))

#define TEXT_WIDTH2(tPtr, start, end) (WMWidthOfString((tPtr)->font, \
    &((tPtr)->text[(start)]), (end) - (start)))

static inline int oneUTF8CharBackward(const char *str, int len)
{
	const unsigned char *ustr = (const unsigned char *)str;
	int pos = 0;

	while (len-- > 0 && ustr[--pos] >= 0x80 && ustr[pos] <= 0xbf) ;
	return pos;
}

static inline int oneUTF8CharForward(const char *str, int len)
{
	const unsigned char *ustr = (const unsigned char *)str;
	int pos = 0;

	while (len-- > 0 && ustr[++pos] >= 0x80 && ustr[pos] <= 0xbf) ;
	return pos;
}

// find the beginning of the UTF8 char pointed by str
static inline int seekUTF8CharStart(const char *str, int len)
{
	const unsigned char *ustr = (const unsigned char *)str;
	int pos = 0;

	while (len-- > 0 && ustr[pos] >= 0x80 && ustr[pos] <= 0xbf)
		--pos;
	return pos;
}

static void normalizeRange(TextField * tPtr, WMRange * range)
{
	if (range->position < 0 && range->count < 0)
		range->count = 0;

	if (range->count == 0) {
		/*range->position = 0; why is this? */
		return;
	}

	/* (1,-2) ~> (0,1) ; (1,-1) ~> (0,1) ; (2,-1) ~> (1,1) */
	if (range->count < 0) {	/* && range->position >= 0 */
		if (range->position + range->count < 0) {
			range->count = range->position;
			range->position = 0;
		} else {
			range->count = -range->count;
			range->position -= range->count;
		}
		/* (-2,1) ~> (0,0) ; (-1,1) ~> (0,0) ; (-1,2) ~> (0,1) */
	} else if (range->position < 0) {	/* && range->count > 0 */
		if (range->position + range->count < 0) {
			range->position = range->count = 0;
		} else {
			range->count += range->position;
			range->position = 0;
		}
	}

	if (range->position + range->count > tPtr->textLen)
		range->count = tPtr->textLen - range->position;
}

static void memmv(char *dest, const char *src, int size)
{
	int i;

	if (dest > src) {
		for (i = size - 1; i >= 0; i--) {
			dest[i] = src[i];
		}
	} else if (dest < src) {
		for (i = 0; i < size; i++) {
			dest[i] = src[i];
		}
	}
}

static int incrToFit(TextField * tPtr)
{
	int vp = tPtr->viewPosition;

	while (TEXT_WIDTH(tPtr, tPtr->viewPosition) > tPtr->usableWidth) {
		tPtr->viewPosition += oneUTF8CharForward(&tPtr->text[tPtr->viewPosition],
							 tPtr->textLen - tPtr->viewPosition);
	}
	return vp != tPtr->viewPosition;
}

static int incrToFit2(TextField * tPtr)
{
	int vp = tPtr->viewPosition;

	while (TEXT_WIDTH2(tPtr, tPtr->viewPosition, tPtr->cursorPosition)
	       >= tPtr->usableWidth)
		tPtr->viewPosition += oneUTF8CharForward(&tPtr->text[tPtr->viewPosition],
							 tPtr->cursorPosition - tPtr->viewPosition);
	return vp != tPtr->viewPosition;
}

static void decrToFit(TextField * tPtr)
{
	int vp = tPtr->viewPosition;

	while (vp > 0 && (vp += oneUTF8CharBackward(&tPtr->text[vp], vp),
			  TEXT_WIDTH(tPtr, vp)) < tPtr->usableWidth) {
		tPtr->viewPosition = vp;
	}
}

#undef TEXT_WIDTH
#undef TEXT_WIDTH2

static WMData *requestHandler(WMView * view, Atom selection, Atom target, void *cdata, Atom * type)
{
	TextField *tPtr = view->self;
	int count;
	Display *dpy = tPtr->view->screen->display;
	Atom _TARGETS;
	Atom TEXT = XInternAtom(dpy, "TEXT", False);
	Atom COMPOUND_TEXT = XInternAtom(dpy, "COMPOUND_TEXT", False);
	WMData *data;

	/* Parameter not used, but tell the compiler that it is ok */
	(void) selection;
	(void) cdata;

	count = tPtr->selection.count < 0
	    ? tPtr->selection.position + tPtr->selection.count : tPtr->selection.position;

	if (target == XA_STRING || target == TEXT || target == COMPOUND_TEXT) {

		data = WMCreateDataWithBytes(&(tPtr->text[count]), abs(tPtr->selection.count));
		WMSetDataFormat(data, 8);
		*type = target;

		return data;
	}

	_TARGETS = XInternAtom(dpy, "TARGETS", False);
	if (target == _TARGETS) {
		Atom supported_type[4];

		supported_type[0] = _TARGETS;
		supported_type[1] = XA_STRING;
		supported_type[2] = TEXT;
		supported_type[3] = COMPOUND_TEXT;

		data = WMCreateDataWithBytes(supported_type, sizeof(supported_type));
		WMSetDataFormat(data, 32);

		*type = target;
		return data;
	}

	return NULL;

}

static void lostSelection(WMView * view, Atom selection, void *cdata)
{
	TextField *tPtr = (WMTextField *) view->self;

	/* Parameter not used, but tell the compiler that it is ok */
	(void) cdata;

	if (tPtr->flags.ownsSelection) {
		WMDeleteSelectionHandler(view, selection, CurrentTime);
		tPtr->flags.ownsSelection = 0;
	}
	if (tPtr->selection.count != 0) {
		tPtr->selection.count = 0;
		paintTextField(tPtr);
	}
}

static void selectionNotification(void *observerData, WMNotification * notification)
{
	WMView *observerView = (WMView *) observerData;
	WMView *newOwnerView = (WMView *) WMGetNotificationClientData(notification);

	if (observerView != newOwnerView) {
		/*
		   //if (tPtr->flags.ownsSelection)
		   //    WMDeleteSelectionHandler(observerView, XA_PRIMARY, CurrentTime);
		 */
		lostSelection(observerView, XA_PRIMARY, NULL);
	}
}

static void realizeObserver(void *self, WMNotification * not)
{
	/* Parameter not used, but tell the compiler that it is ok */
	(void) not;

	W_CreateIC(((TextField *) self)->view);
}

WMTextField *WMCreateTextField(WMWidget * parent)
{
	TextField *tPtr;

	tPtr = wmalloc(sizeof(TextField));
	tPtr->widgetClass = WC_TextField;

	tPtr->view = W_CreateView(W_VIEW(parent));
	if (!tPtr->view) {
		wfree(tPtr);
		return NULL;
	}
	tPtr->view->self = tPtr;

	tPtr->view->delegate = &_TextFieldViewDelegate;

	tPtr->view->attribFlags |= CWCursor;
	tPtr->view->attribs.cursor = tPtr->view->screen->textCursor;

	W_SetViewBackgroundColor(tPtr->view, tPtr->view->screen->white);

	tPtr->text = wmalloc(MIN_TEXT_BUFFER);
	tPtr->textLen = 0;
	tPtr->bufferSize = MIN_TEXT_BUFFER;

	tPtr->flags.enabled = 1;

	WMCreateEventHandler(tPtr->view, ExposureMask | StructureNotifyMask | FocusChangeMask, handleEvents, tPtr);

	tPtr->font = WMRetainFont(tPtr->view->screen->normalFont);

	tPtr->flags.bordered = DEFAULT_BORDERED;
	tPtr->flags.beveled = True;
	tPtr->flags.alignment = DEFAULT_ALIGNMENT;
	tPtr->offsetWidth = WMAX((tPtr->view->size.height - WMFontHeight(tPtr->font)) / 2, 1);

	W_ResizeView(tPtr->view, DEFAULT_WIDTH, DEFAULT_HEIGHT);

	WMCreateEventHandler(tPtr->view, EnterWindowMask | LeaveWindowMask
			     | ButtonReleaseMask | ButtonPressMask | KeyPressMask | Button1MotionMask,
			     handleTextFieldActionEvents, tPtr);

	WMAddNotificationObserver(selectionNotification, tPtr->view,
				  WMSelectionOwnerDidChangeNotification, (void *)XA_PRIMARY);

	WMAddNotificationObserver(realizeObserver, tPtr, WMViewRealizedNotification, tPtr->view);

	tPtr->flags.cursorOn = 1;

	return tPtr;
}

void WMSetTextFieldDelegate(WMTextField * tPtr, WMTextFieldDelegate * delegate)
{
	CHECK_CLASS(tPtr, WC_TextField);

	tPtr->delegate = delegate;
}

WMTextFieldDelegate *WMGetTextFieldDelegate(WMTextField * tPtr)
{
	CHECK_CLASS(tPtr, WC_TextField);

	return tPtr->delegate;
}

void WMInsertTextFieldText(WMTextField * tPtr, const char *text, int position)
{
	int len;

	CHECK_CLASS(tPtr, WC_TextField);

	if (!text)
		return;

	len = strlen(text);

	/* check if buffer will hold the text */
	if (len + tPtr->textLen >= tPtr->bufferSize) {
		tPtr->bufferSize = tPtr->textLen + len + TEXT_BUFFER_INCR;
		tPtr->text = wrealloc(tPtr->text, tPtr->bufferSize);
	}

	if (position < 0 || position >= tPtr->textLen) {
		/* append the text at the end */
		wstrlcat(tPtr->text, text, tPtr->bufferSize);
		tPtr->textLen += len;
		tPtr->cursorPosition += len;
		incrToFit(tPtr);
	} else {
		/* insert text at position */
		memmv(&(tPtr->text[position + len]), &(tPtr->text[position]), tPtr->textLen - position + 1);

		memcpy(&(tPtr->text[position]), text, len);

		tPtr->textLen += len;
		if (position >= tPtr->cursorPosition) {
			tPtr->cursorPosition += len;
			incrToFit2(tPtr);
		} else {
			incrToFit(tPtr);
		}
	}

	paintTextField(tPtr);
}

void WMDeleteTextFieldRange(WMTextField * tPtr, WMRange range)
{
	CHECK_CLASS(tPtr, WC_TextField);

	normalizeRange(tPtr, &range);

	if (!range.count)
		return;

	memmv(&(tPtr->text[range.position]), &(tPtr->text[range.position + range.count]),
	      tPtr->textLen - (range.position + range.count) + 1);

	/* better than nothing ;) */
	if (tPtr->cursorPosition > range.position)
		tPtr->viewPosition += oneUTF8CharBackward(&tPtr->text[tPtr->viewPosition], tPtr->viewPosition);
	tPtr->textLen -= range.count;
	tPtr->cursorPosition = range.position;

	decrToFit(tPtr);

	paintTextField(tPtr);
}

char *WMGetTextFieldText(WMTextField * tPtr)
{
	CHECK_CLASS(tPtr, WC_TextField);

	return wstrdup(tPtr->text);
}

void WMSetTextFieldText(WMTextField * tPtr, const char *text)
{
	CHECK_CLASS(tPtr, WC_TextField);

	if ((text && strcmp(tPtr->text, text) == 0) || (!text && tPtr->textLen == 0))
		return;

	if (text == NULL) {
		tPtr->text[0] = 0;
		tPtr->textLen = 0;
	} else {
		tPtr->textLen = strlen(text);

		if (tPtr->textLen >= tPtr->bufferSize) {
			tPtr->bufferSize = tPtr->textLen + TEXT_BUFFER_INCR;
			tPtr->text = wrealloc(tPtr->text, tPtr->bufferSize);
		}
		wstrlcpy(tPtr->text, text, tPtr->bufferSize);
	}

	tPtr->cursorPosition = tPtr->selection.position = tPtr->textLen;
	tPtr->viewPosition = 0;
	tPtr->selection.count = 0;

	if (tPtr->view->flags.realized)
		paintTextField(tPtr);
}

void WMSetTextFieldAlignment(WMTextField * tPtr, WMAlignment alignment)
{
	CHECK_CLASS(tPtr, WC_TextField);

	tPtr->flags.alignment = alignment;

	if (alignment != WALeft) {
		wwarning(_("only left alignment is supported in textfields"));
		return;
	}

	if (tPtr->view->flags.realized) {
		paintTextField(tPtr);
	}
}

void WMSetTextFieldBordered(WMTextField * tPtr, Bool bordered)
{
	CHECK_CLASS(tPtr, WC_TextField);

	tPtr->flags.bordered = bordered;

	if (tPtr->view->flags.realized) {
		paintTextField(tPtr);
	}
}

void WMSetTextFieldBeveled(WMTextField * tPtr, Bool flag)
{
	CHECK_CLASS(tPtr, WC_TextField);

	tPtr->flags.beveled = ((flag == 0) ? 0 : 1);

	if (tPtr->view->flags.realized) {
		paintTextField(tPtr);
	}
}

void WMSetTextFieldSecure(WMTextField * tPtr, Bool flag)
{
	CHECK_CLASS(tPtr, WC_TextField);

	tPtr->flags.secure = ((flag == 0) ? 0 : 1);

	if (tPtr->view->flags.realized) {
		paintTextField(tPtr);
	}
}

Bool WMGetTextFieldEditable(WMTextField * tPtr)
{
	CHECK_CLASS(tPtr, WC_TextField);

	return tPtr->flags.enabled;
}

void WMSetTextFieldEditable(WMTextField * tPtr, Bool flag)
{
	CHECK_CLASS(tPtr, WC_TextField);

	tPtr->flags.enabled = ((flag == 0) ? 0 : 1);

	if (tPtr->view->flags.realized) {
		paintTextField(tPtr);
	}
}

void WMSelectTextFieldRange(WMTextField * tPtr, WMRange range)
{
	CHECK_CLASS(tPtr, WC_TextField);

	if (tPtr->flags.enabled) {
		normalizeRange(tPtr, &range);

		tPtr->selection = range;

		tPtr->cursorPosition = range.position + range.count;

		if (tPtr->view->flags.realized) {
			paintTextField(tPtr);
		}
	}
}

void WMSetTextFieldCursorPosition(WMTextField * tPtr, unsigned int position)
{
	CHECK_CLASS(tPtr, WC_TextField);

	if (tPtr->flags.enabled) {
		if (position > tPtr->textLen)
			position = tPtr->textLen;

		tPtr->cursorPosition = position;
		if (tPtr->view->flags.realized) {
			paintTextField(tPtr);
		}
	}
}

unsigned WMGetTextFieldCursorPosition(WMTextField *tPtr)
{
	CHECK_CLASS(tPtr, WC_TextField);

	return tPtr->cursorPosition;
}

void WMSetTextFieldNextTextField(WMTextField * tPtr, WMTextField * next)
{
	CHECK_CLASS(tPtr, WC_TextField);
	if (next == NULL) {
		if (tPtr->view->nextFocusChain)
			tPtr->view->nextFocusChain->prevFocusChain = NULL;
		tPtr->view->nextFocusChain = NULL;
		return;
	}

	CHECK_CLASS(next, WC_TextField);

	if (tPtr->view->nextFocusChain)
		tPtr->view->nextFocusChain->prevFocusChain = NULL;
	if (next->view->prevFocusChain)
		next->view->prevFocusChain->nextFocusChain = NULL;

	tPtr->view->nextFocusChain = next->view;
	next->view->prevFocusChain = tPtr->view;
}

void WMSetTextFieldPrevTextField(WMTextField * tPtr, WMTextField * prev)
{
	CHECK_CLASS(tPtr, WC_TextField);
	if (prev == NULL) {
		if (tPtr->view->prevFocusChain)
			tPtr->view->prevFocusChain->nextFocusChain = NULL;
		tPtr->view->prevFocusChain = NULL;
		return;
	}

	CHECK_CLASS(prev, WC_TextField);

	if (tPtr->view->prevFocusChain)
		tPtr->view->prevFocusChain->nextFocusChain = NULL;
	if (prev->view->nextFocusChain)
		prev->view->nextFocusChain->prevFocusChain = NULL;

	tPtr->view->prevFocusChain = prev->view;
	prev->view->nextFocusChain = tPtr->view;
}

void WMSetTextFieldFont(WMTextField * tPtr, WMFont * font)
{
	CHECK_CLASS(tPtr, WC_TextField);

	if (tPtr->font)
		WMReleaseFont(tPtr->font);
	tPtr->font = WMRetainFont(font);

	tPtr->offsetWidth = WMAX((tPtr->view->size.height - WMFontHeight(tPtr->font)) / 2, 1);

	if (tPtr->view->flags.realized) {
		paintTextField(tPtr);
	}
}

WMFont *WMGetTextFieldFont(WMTextField * tPtr)
{
	return tPtr->font;
}

static void didResizeTextField(W_ViewDelegate * self, WMView * view)
{
	WMTextField *tPtr = (WMTextField *) view->self;

	/* Parameter not used, but tell the compiler that it is ok */
	(void) self;

	tPtr->offsetWidth = WMAX((tPtr->view->size.height - WMFontHeight(tPtr->font)) / 2, 1);

	tPtr->usableWidth = tPtr->view->size.width - 2 * tPtr->offsetWidth;
}

static char *makeHiddenString(int length)
{
	char *data = wmalloc(length + 1);

	memset(data, '*', length);
	data[length] = '\0';

	return data;
}

static void paintCursor(TextField * tPtr)
{
	int cx;
	WMScreen *screen = tPtr->view->screen;
	int textWidth;
	char *text;

	if (tPtr->flags.secure)
		text = makeHiddenString(strlen(tPtr->text));
	else
		text = tPtr->text;

	cx = WMWidthOfString(tPtr->font, &(text[tPtr->viewPosition]), tPtr->cursorPosition - tPtr->viewPosition);

	switch (tPtr->flags.alignment) {
	case WARight:
		textWidth = WMWidthOfString(tPtr->font, text, tPtr->textLen);
		if (textWidth < tPtr->usableWidth)
			cx += tPtr->offsetWidth + tPtr->usableWidth - textWidth + 1;
		else
			cx += tPtr->offsetWidth + 1;
		break;
	case WALeft:
		cx += tPtr->offsetWidth + 1;
		break;
	case WAJustified:
		/* not supported */
	case WACenter:
		textWidth = WMWidthOfString(tPtr->font, text, tPtr->textLen);
		if (textWidth < tPtr->usableWidth)
			cx += tPtr->offsetWidth + (tPtr->usableWidth - textWidth) / 2;
		else
			cx += tPtr->offsetWidth;
		break;
	}
	/*
	   XDrawRectangle(screen->display, tPtr->view->window, screen->xorGC,
	   cx, tPtr->offsetWidth, 1,
	   tPtr->view->size.height - 2*tPtr->offsetWidth - 1);
	   printf("%d %d\n",cx,tPtr->cursorPosition);
	 */

	XDrawLine(screen->display, tPtr->view->window, screen->xorGC,
		  cx, tPtr->offsetWidth, cx, tPtr->view->size.height - tPtr->offsetWidth - 1);

	W_SetPreeditPositon(tPtr->view, cx, 0);

	if (tPtr->flags.secure) {
		wfree(text);
	}
}

static void drawRelief(WMView * view, Bool beveled)
{
	WMScreen *scr = view->screen;
	Display *dpy = scr->display;
	GC wgc;
	GC lgc;
	GC dgc;
	int width = view->size.width;
	int height = view->size.height;

	dgc = WMColorGC(scr->darkGray);

	if (!beveled) {
		XDrawRectangle(dpy, view->window, dgc, 0, 0, width - 1, height - 1);

		return;
	}
	wgc = WMColorGC(scr->white);
	lgc = WMColorGC(scr->gray);

	/* top left */
	XDrawLine(dpy, view->window, dgc, 0, 0, width - 1, 0);
	XDrawLine(dpy, view->window, dgc, 0, 1, width - 2, 1);

	XDrawLine(dpy, view->window, dgc, 0, 0, 0, height - 2);
	XDrawLine(dpy, view->window, dgc, 1, 0, 1, height - 3);

	/* bottom right */
	XDrawLine(dpy, view->window, wgc, 0, height - 1, width - 1, height - 1);
	XDrawLine(dpy, view->window, lgc, 1, height - 2, width - 2, height - 2);

	XDrawLine(dpy, view->window, wgc, width - 1, 0, width - 1, height - 1);
	XDrawLine(dpy, view->window, lgc, width - 2, 1, width - 2, height - 3);
}

static void paintTextField(TextField * tPtr)
{
	W_Screen *screen = tPtr->view->screen;
	W_View *view = tPtr->view;
	W_View viewbuffer;
	int tx, ty, tw;
	int rx;
	int bd;
	int totalWidth;
	char *text;
	Pixmap drawbuffer;
	WMColor *color;

	if (!view->flags.realized || !view->flags.mapped)
		return;

	if (!tPtr->flags.bordered) {
		bd = 0;
	} else {
		bd = 2;
	}

	if (tPtr->flags.secure) {
		text = makeHiddenString(strlen(tPtr->text));
	} else {
		text = tPtr->text;
	}

	totalWidth = tPtr->view->size.width - 2 * bd;

	drawbuffer = XCreatePixmap(screen->display, view->window,
				   view->size.width, view->size.height, screen->depth);
	XFillRectangle(screen->display, drawbuffer, WMColorGC(screen->white),
		       0, 0, view->size.width, view->size.height);
	/* this is quite dirty */
	viewbuffer.screen = view->screen;
	viewbuffer.size = view->size;
	viewbuffer.window = drawbuffer;

	if (tPtr->textLen > 0) {
		tw = WMWidthOfString(tPtr->font, &(text[tPtr->viewPosition]), tPtr->textLen - tPtr->viewPosition);

		ty = tPtr->offsetWidth;
		switch (tPtr->flags.alignment) {
		case WALeft:
			tx = tPtr->offsetWidth + 1;
			if (tw < tPtr->usableWidth)
				XFillRectangle(screen->display, drawbuffer,
					       WMColorGC(screen->white),
					       bd + tw, bd, totalWidth - tw, view->size.height - 2 * bd);
			break;

		case WACenter:
			tx = tPtr->offsetWidth + (tPtr->usableWidth - tw) / 2;
			if (tw < tPtr->usableWidth)
				XClearArea(screen->display, view->window, bd, bd,
					   totalWidth, view->size.height - 2 * bd, False);
			break;

		default:
		case WARight:
			tx = tPtr->offsetWidth + tPtr->usableWidth - tw - 1;
			if (tw < tPtr->usableWidth)
				XClearArea(screen->display, view->window, bd, bd,
					   totalWidth - tw, view->size.height - 2 * bd, False);
			break;
		}

		color = tPtr->flags.enabled ? screen->black : screen->darkGray;

		WMDrawImageString(screen, drawbuffer, color, screen->white,
				  tPtr->font, tx, ty, &(text[tPtr->viewPosition]),
				  tPtr->textLen - tPtr->viewPosition);

		if (tPtr->selection.count) {
			int count, count2;

			count = tPtr->selection.count < 0
			    ? tPtr->selection.position + tPtr->selection.count : tPtr->selection.position;
			count2 = abs(tPtr->selection.count);
			if (count < tPtr->viewPosition) {
				count2 = abs(count2 - abs(tPtr->viewPosition - count));
				count = tPtr->viewPosition;
			}

			rx = tPtr->offsetWidth + 1 + WMWidthOfString(tPtr->font, text, count)
			    - WMWidthOfString(tPtr->font, text, tPtr->viewPosition);

			WMDrawImageString(screen, drawbuffer, color, screen->gray,
					  tPtr->font, rx, ty, &(text[count]), count2);
		}
	} else {
		XFillRectangle(screen->display, drawbuffer, WMColorGC(screen->white),
			       bd, bd, totalWidth, view->size.height - 2 * bd);
	}

	/* draw relief */
	if (tPtr->flags.bordered) {
		drawRelief(&viewbuffer, tPtr->flags.beveled);
	}

	if (tPtr->flags.secure)
		wfree(text);
	XCopyArea(screen->display, drawbuffer, view->window,
		  screen->copyGC, 0, 0, view->size.width, view->size.height, 0, 0);
	XFreePixmap(screen->display, drawbuffer);

	/* draw cursor */
	if (tPtr->flags.focused && tPtr->flags.enabled && tPtr->flags.cursorOn) {
		paintCursor(tPtr);
	}
}

static void blinkCursor(void *data)
{
	TextField *tPtr = (TextField *) data;

	if (tPtr->flags.cursorOn) {
		tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_OFF_DELAY, blinkCursor, data);
	} else {
		tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_ON_DELAY, blinkCursor, data);
	}
	paintCursor(tPtr);
	tPtr->flags.cursorOn = !tPtr->flags.cursorOn;
}

static void handleEvents(XEvent * event, void *data)
{
	TextField *tPtr = (TextField *) data;

	CHECK_CLASS(data, WC_TextField);

	switch (event->type) {
	case FocusIn:
		W_FocusIC(tPtr->view);
		if (W_FocusedViewOfToplevel(W_TopLevelOfView(tPtr->view)) != tPtr->view)
			return;
		tPtr->flags.focused = 1;

		if (!tPtr->timerID) {
			tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_ON_DELAY, blinkCursor, tPtr);
		}

		paintTextField(tPtr);

		NOTIFY(tPtr, didBeginEditing, WMTextDidBeginEditingNotification, NULL);

		tPtr->flags.notIllegalMovement = 0;
		break;

	case FocusOut:
		W_UnFocusIC(tPtr->view);
		tPtr->flags.focused = 0;

		if (tPtr->timerID)
			WMDeleteTimerHandler(tPtr->timerID);
		tPtr->timerID = NULL;

		paintTextField(tPtr);
		if (!tPtr->flags.notIllegalMovement) {
			NOTIFY(tPtr, didEndEditing, WMTextDidEndEditingNotification,
			       (void *)WMIllegalTextMovement);
		}
		break;

	case Expose:
		if (event->xexpose.count != 0)
			break;
		paintTextField(tPtr);
		break;

	case DestroyNotify:
		destroyTextField(tPtr);
		break;
	}
}

static void handleTextFieldKeyPress(TextField * tPtr, XEvent * event)
{
	char buffer[64];
	KeySym ksym;
	const char *textEvent = NULL;
	void *data = NULL;
	int count, refresh = 0;
	int control_pressed = 0;
	int cancelSelection = 1;
	Bool shifted, controled, modified;
	Bool relay = True;

	/*printf("(%d,%d) -> ", tPtr->selection.position, tPtr->selection.count); */
	if (((XKeyEvent *) event)->state & WM_EMACSKEYMASK)
		control_pressed = 1;

	shifted = (event->xkey.state & ShiftMask ? True : False);
	controled = (event->xkey.state & ControlMask ? True : False);
	modified = shifted || controled;

	count = W_LookupString(tPtr->view, &event->xkey, buffer, 63, &ksym, NULL);
	//count = XLookupString(&event->xkey, buffer, 63, &ksym, NULL);
	buffer[count] = '\0';

	switch (ksym) {
	case XK_Tab:
#ifdef XK_ISO_Left_Tab
		/* FALLTHRU */
	case XK_ISO_Left_Tab:
#endif
		if (!controled) {
			if (shifted) {
				if (tPtr->view->prevFocusChain) {
					W_SetFocusOfTopLevel(W_TopLevelOfView(tPtr->view),
							     tPtr->view->prevFocusChain);
					tPtr->flags.notIllegalMovement = 1;
				}
				data = (void *)WMBacktabTextMovement;
			} else {
				if (tPtr->view->nextFocusChain) {
					W_SetFocusOfTopLevel(W_TopLevelOfView(tPtr->view),
							     tPtr->view->nextFocusChain);
					tPtr->flags.notIllegalMovement = 1;
				}
				data = (void *)WMTabTextMovement;
			}
			textEvent = WMTextDidEndEditingNotification;

			cancelSelection = 0;

			relay = False;
		}
		break;

	case XK_Escape:
		if (!modified) {
			data = (void *)WMEscapeTextMovement;
			textEvent = WMTextDidEndEditingNotification;

			relay = False;
		}
		break;

#ifdef XK_KP_Enter
		/* FALLTHRU */
	case XK_KP_Enter:
#endif
		/* FALLTHRU */
	case XK_Return:
		if (!modified) {
			data = (void *)WMReturnTextMovement;
			textEvent = WMTextDidEndEditingNotification;

			relay = False;
		}
		break;

	case WM_EMACSKEY_LEFT:
		if (!control_pressed)
			goto normal_key;
		else
			controled = False;

#ifdef XK_KP_Left
		/* FALLTHRU */
	case XK_KP_Left:
#endif
		/* FALLTHRU */
	case XK_Left:
		if (tPtr->cursorPosition > 0) {
			int i;
			paintCursor(tPtr);

			i = tPtr->cursorPosition;
			i += oneUTF8CharBackward(&tPtr->text[i], i);
			if (controled) {
				while (i > 0 && tPtr->text[i] != ' ')
					i--;
				while (i > 0 && tPtr->text[i] == ' ')
					i--;

				tPtr->cursorPosition = (i > 0) ? i + 1 : 0;
			} else
				tPtr->cursorPosition = i;

			if (tPtr->cursorPosition < tPtr->viewPosition) {
				tPtr->viewPosition = tPtr->cursorPosition;
				refresh = 1;
			} else
				paintCursor(tPtr);
		}
		if (shifted)
			cancelSelection = 0;

		relay = False;

		break;

	case WM_EMACSKEY_RIGHT:
		if (!control_pressed)
			goto normal_key;
		else
			controled = False;

#ifdef XK_KP_Right
		/* FALLTHRU */
	case XK_KP_Right:
#endif
		/* FALLTHRU */
	case XK_Right:
		if (tPtr->cursorPosition < tPtr->textLen) {
			int i;
			paintCursor(tPtr);

			i = tPtr->cursorPosition;
			if (controled) {
				while (tPtr->text[i] && tPtr->text[i] != ' ')
					i++;
				while (tPtr->text[i] == ' ')
					i++;
			} else {
				i += oneUTF8CharForward(&tPtr->text[i], tPtr->textLen - i);
			}
			tPtr->cursorPosition = i;

			refresh = incrToFit2(tPtr);

			if (!refresh)
				paintCursor(tPtr);
		}
		if (shifted)
			cancelSelection = 0;

		relay = False;

		break;

	case WM_EMACSKEY_HOME:
		if (!control_pressed)
			goto normal_key;
		else
			controled = False;

#ifdef XK_KP_Home
		/* FALLTHRU */
	case XK_KP_Home:
#endif
		/* FALLTHRU */
	case XK_Home:
		if (!controled) {
			if (tPtr->cursorPosition > 0) {
				paintCursor(tPtr);
				tPtr->cursorPosition = 0;
				if (tPtr->viewPosition > 0) {
					tPtr->viewPosition = 0;
					refresh = 1;
				} else
					paintCursor(tPtr);
			}
			if (shifted)
				cancelSelection = 0;

			relay = False;
		}
		break;

	case WM_EMACSKEY_END:
		if (!control_pressed)
			goto normal_key;
		else
			controled = False;

#ifdef XK_KP_End
		/* FALLTHRU */
	case XK_KP_End:
#endif
		/* FALLTHRU */
	case XK_End:
		if (!controled) {
			if (tPtr->cursorPosition < tPtr->textLen) {
				paintCursor(tPtr);
				tPtr->cursorPosition = tPtr->textLen;
				tPtr->viewPosition = 0;

				refresh = incrToFit(tPtr);

				if (!refresh)
					paintCursor(tPtr);
			}
			if (shifted)
				cancelSelection = 0;

			relay = False;
		}
		break;

	case WM_EMACSKEY_BS:
		if (!control_pressed)
			goto normal_key;
		else
			modified = False;

		/* FALLTHRU */
	case XK_BackSpace:
		if (!modified) {
			if (tPtr->selection.count) {
				WMDeleteTextFieldRange(tPtr, tPtr->selection);
				data = (void *)WMDeleteTextEvent;
				textEvent = WMTextDidChangeNotification;
			} else if (tPtr->cursorPosition > 0) {
				int i = oneUTF8CharBackward(&tPtr->text[tPtr->cursorPosition],
							    tPtr->cursorPosition);
				WMRange range;
				range.position = tPtr->cursorPosition + i;
				range.count = -i;
				WMDeleteTextFieldRange(tPtr, range);
				data = (void *)WMDeleteTextEvent;
				textEvent = WMTextDidChangeNotification;
			}

			relay = False;
		}
		break;

	case WM_EMACSKEY_DEL:
		if (!control_pressed)
			goto normal_key;
		else
			modified = False;

#ifdef XK_KP_Delete
		/* FALLTHRU */
	case XK_KP_Delete:
#endif
		/* FALLTHRU */
	case XK_Delete:
		if (!modified) {
			if (tPtr->selection.count) {
				WMDeleteTextFieldRange(tPtr, tPtr->selection);
				data = (void *)WMDeleteTextEvent;
				textEvent = WMTextDidChangeNotification;
			} else if (tPtr->cursorPosition < tPtr->textLen) {
				WMRange range;
				range.position = tPtr->cursorPosition;
				range.count = oneUTF8CharForward(&tPtr->text[tPtr->cursorPosition],
								 tPtr->textLen - tPtr->cursorPosition);
				WMDeleteTextFieldRange(tPtr, range);
				data = (void *)WMDeleteTextEvent;
				textEvent = WMTextDidChangeNotification;
			}

			relay = False;
		}
		break;

 normal_key:
	default:
		if (!controled) {
			if (count > 0 && !iscntrl(buffer[0])) {
				if (tPtr->selection.count)
					WMDeleteTextFieldRange(tPtr, tPtr->selection);
				WMInsertTextFieldText(tPtr, buffer, tPtr->cursorPosition);
				data = (void *)WMInsertTextEvent;
				textEvent = WMTextDidChangeNotification;

				relay = False;
			}
		}
		break;
	}

	if (relay) {
		WMRelayToNextResponder(W_VIEW(tPtr), event);
		return;
	}

	/* Do not allow text selection in secure text fields */
	if (cancelSelection || tPtr->flags.secure) {
		lostSelection(tPtr->view, XA_PRIMARY, NULL);

		if (tPtr->selection.count) {
			tPtr->selection.count = 0;
			refresh = 1;
		}
		tPtr->selection.position = tPtr->cursorPosition;
	} else {
		if (tPtr->selection.count != tPtr->cursorPosition - tPtr->selection.position) {

			tPtr->selection.count = tPtr->cursorPosition - tPtr->selection.position;

			refresh = 1;
		}
	}

	/*printf("(%d,%d)\n", tPtr->selection.position, tPtr->selection.count); */

	if (textEvent) {
		WMNotification *notif = WMCreateNotification(textEvent, tPtr, data);

		if (tPtr->delegate) {
			if (textEvent == WMTextDidBeginEditingNotification && tPtr->delegate->didBeginEditing)
				(*tPtr->delegate->didBeginEditing) (tPtr->delegate, notif);

			else if (textEvent == WMTextDidEndEditingNotification && tPtr->delegate->didEndEditing)
				(*tPtr->delegate->didEndEditing) (tPtr->delegate, notif);

			else if (textEvent == WMTextDidChangeNotification && tPtr->delegate->didChange)
				(*tPtr->delegate->didChange) (tPtr->delegate, notif);
		}

		WMPostNotification(notif);
		WMReleaseNotification(notif);
	}

	if (refresh)
		paintTextField(tPtr);

	/*printf("(%d,%d)\n", tPtr->selection.position, tPtr->selection.count); */
}

static int pointToCursorPosition(TextField * tPtr, int x)
{
	int a, b, pos, prev, tw;

	if (tPtr->flags.bordered)
		x -= 2;

	if (WMWidthOfString(tPtr->font, &(tPtr->text[tPtr->viewPosition]),
			    tPtr->textLen - tPtr->viewPosition) <= x)
		return tPtr->textLen;

	a = tPtr->viewPosition;
	b = tPtr->textLen;

	/* we halve the text until we get into a 10 byte vicinity of x */
	while (b - a > 10) {
		pos = (a + b) / 2;
		pos += seekUTF8CharStart(&tPtr->text[pos], pos - a);
		tw = WMWidthOfString(tPtr->font, &(tPtr->text[tPtr->viewPosition]), pos - tPtr->viewPosition);
		if (tw > x) {
			b = pos;
		} else if (tw < x) {
			a = pos;
		} else {
			return pos;
		}
	}

	/* at this point x can be positioned on any glyph between 'a' and 'b-1'
	 * inclusive, with the exception of the left border of the 'a' glyph and
	 * the right border or the 'b-1' glyph
	 *
	 * ( <--- range for x's position ---> )
	 * a a+1 .......................... b-1 b
	 */
	pos = prev = a;
	while (pos <= b) {
		tw = WMWidthOfString(tPtr->font, &(tPtr->text[tPtr->viewPosition]), pos - tPtr->viewPosition);
		if (tw > x) {
			return prev;
		} else if (pos == b) {
			break;
		}
		prev = pos;
		pos += oneUTF8CharForward(&tPtr->text[pos], b - pos);
	}

	return b;
}

static void pasteText(WMView * view, Atom selection, Atom target, Time timestamp, void *cdata, WMData * data)
{
	TextField *tPtr = (TextField *) view->self;
	char *str;

	/* Parameter not used, but tell the compiler that it is ok */
	(void) selection;
	(void) target;
	(void) timestamp;
	(void) cdata;

	tPtr->flags.waitingSelection = 0;

	if (data != NULL) {
		str = (char *)WMDataBytes(data);

		WMInsertTextFieldText(tPtr, str, tPtr->cursorPosition);
		NOTIFY(tPtr, didChange, WMTextDidChangeNotification, (void *)WMInsertTextEvent);
	} else {
		int n;

		str = XFetchBuffer(tPtr->view->screen->display, &n, 0);

		if (str != NULL) {
			str[n] = 0;
			WMInsertTextFieldText(tPtr, str, tPtr->cursorPosition);
			XFree(str);
			NOTIFY(tPtr, didChange, WMTextDidChangeNotification, (void *)WMInsertTextEvent);
		}
	}
}

static void handleTextFieldActionEvents(XEvent * event, void *data)
{
	TextField *tPtr = (TextField *) data;
	static Time lastButtonReleasedEvent = 0;
	static Time lastButtonReleasedEvent2 = 0;
	Display *dpy = event->xany.display;

	CHECK_CLASS(data, WC_TextField);

	switch (event->type) {
	case KeyPress:
		if (tPtr->flags.waitingSelection) {
			return;
		}
		if (tPtr->flags.enabled && tPtr->flags.focused) {
			handleTextFieldKeyPress(tPtr, event);
			XDefineCursor(dpy, W_VIEW(tPtr)->window, W_VIEW(tPtr)->screen->invisibleCursor);
			tPtr->flags.pointerGrabbed = 1;
		}
		break;

	case MotionNotify:

		if (tPtr->flags.pointerGrabbed) {
			tPtr->flags.pointerGrabbed = 0;
			XDefineCursor(dpy, W_VIEW(tPtr)->window, W_VIEW(tPtr)->screen->textCursor);
		}
		if (tPtr->flags.waitingSelection) {
			return;
		}

		if (tPtr->flags.enabled && (event->xmotion.state & Button1Mask)) {

			if (tPtr->viewPosition < tPtr->textLen && event->xmotion.x > tPtr->usableWidth) {
				if (WMWidthOfString(tPtr->font,
						    &(tPtr->text[tPtr->viewPosition]),
						    tPtr->cursorPosition - tPtr->viewPosition)
				    > tPtr->usableWidth) {
					tPtr->viewPosition += oneUTF8CharForward(&tPtr->text[tPtr->viewPosition],
										 tPtr->textLen -
										 tPtr->viewPosition);
				}
			} else if (tPtr->viewPosition > 0 && event->xmotion.x < 0) {
				paintCursor(tPtr);
				tPtr->viewPosition += oneUTF8CharBackward(&tPtr->text[tPtr->viewPosition],
									  tPtr->viewPosition);
			}

			tPtr->cursorPosition = pointToCursorPosition(tPtr, event->xmotion.x);

			/* Do not allow text selection in secure textfields */
			if (tPtr->flags.secure) {
				tPtr->selection.position = tPtr->cursorPosition;
			}

			tPtr->selection.count = tPtr->cursorPosition - tPtr->selection.position;

			paintCursor(tPtr);
			paintTextField(tPtr);

		}
		break;

	case ButtonPress:
		if (tPtr->flags.pointerGrabbed) {
			tPtr->flags.pointerGrabbed = 0;
			XDefineCursor(dpy, W_VIEW(tPtr)->window, W_VIEW(tPtr)->screen->textCursor);
			break;
		}

		if (tPtr->flags.waitingSelection) {
			break;
		}

		switch (tPtr->flags.alignment) {
			int textWidth;
		case WARight:
			textWidth = WMWidthOfString(tPtr->font, tPtr->text, tPtr->textLen);
			if (tPtr->flags.enabled && !tPtr->flags.focused) {
				WMSetFocusToWidget(tPtr);
			}
			if (tPtr->flags.focused) {
				tPtr->selection.position = tPtr->cursorPosition;
				tPtr->selection.count = 0;
			}
			if (textWidth < tPtr->usableWidth) {
				tPtr->cursorPosition = pointToCursorPosition(tPtr,
									     event->xbutton.x - tPtr->usableWidth
									     + textWidth);
			} else
				tPtr->cursorPosition = pointToCursorPosition(tPtr, event->xbutton.x);

			paintTextField(tPtr);
			break;

		case WALeft:
			if (tPtr->flags.enabled && !tPtr->flags.focused) {
				WMSetFocusToWidget(tPtr);
			}
			if (tPtr->flags.focused && event->xbutton.button == Button1) {
				tPtr->cursorPosition = pointToCursorPosition(tPtr, event->xbutton.x);
				tPtr->selection.position = tPtr->cursorPosition;
				tPtr->selection.count = 0;
				paintTextField(tPtr);
			}
			if (event->xbutton.button == Button2 && tPtr->flags.enabled) {
				char *text;
				int n;

				if (!WMRequestSelection(tPtr->view, XA_PRIMARY, XA_STRING,
							event->xbutton.time, pasteText, NULL)) {
					text = XFetchBuffer(tPtr->view->screen->display, &n, 0);

					if (text) {
						text[n] = 0;
						WMInsertTextFieldText(tPtr, text, tPtr->cursorPosition);
						XFree(text);
						NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
						       (void *)WMInsertTextEvent);
					}
				} else {
					tPtr->flags.waitingSelection = 1;
				}
			}
			break;
		default:
			break;
		}
		break;

	case ButtonRelease:
		if (tPtr->flags.pointerGrabbed) {
			tPtr->flags.pointerGrabbed = 0;
			XDefineCursor(dpy, W_VIEW(tPtr)->window, W_VIEW(tPtr)->screen->textCursor);
		}
		if (tPtr->flags.waitingSelection) {
			break;
		}

		if (!tPtr->flags.secure && tPtr->selection.count != 0) {
			int start, count;
			XRotateBuffers(dpy, 1);

			count = abs(tPtr->selection.count);
			if (tPtr->selection.count < 0)
				start = tPtr->selection.position - count;
			else
				start = tPtr->selection.position;

			XStoreBuffer(dpy, &tPtr->text[start], count, 0);
		}

		if (!tPtr->flags.secure &&
		    event->xbutton.time - lastButtonReleasedEvent <= WINGsConfiguration.doubleClickDelay) {

			if (event->xbutton.time - lastButtonReleasedEvent2 <=
			    2 * WINGsConfiguration.doubleClickDelay) {
				tPtr->selection.position = 0;
				tPtr->selection.count = tPtr->textLen;
			} else {
				int pos, cnt;
				char *txt;
				pos = tPtr->selection.position;
				cnt = tPtr->selection.count;
				txt = tPtr->text;
				while (pos >= 0) {
					if (txt[pos] == ' ' || txt[pos] == '\t')
						break;
					pos--;
				}
				pos++;

				while (pos + cnt < tPtr->textLen) {
					if (txt[pos + cnt] == ' ' || txt[pos + cnt] == '\t')
						break;
					cnt++;
				}
				tPtr->selection.position = pos;
				tPtr->selection.count = cnt;
			}
			paintTextField(tPtr);

			if (!tPtr->flags.ownsSelection) {
				tPtr->flags.ownsSelection =
				    WMCreateSelectionHandler(tPtr->view,
							     XA_PRIMARY,
							     event->xbutton.time, &selectionHandler, NULL);
			}
		} else if (!tPtr->flags.secure && tPtr->selection.count != 0 && !tPtr->flags.ownsSelection) {
			tPtr->flags.ownsSelection =
			    WMCreateSelectionHandler(tPtr->view,
						     XA_PRIMARY, event->xbutton.time, &selectionHandler, NULL);
		}

		lastButtonReleasedEvent2 = lastButtonReleasedEvent;
		lastButtonReleasedEvent = event->xbutton.time;

		break;
	}
}

static void destroyTextField(TextField * tPtr)
{
	if (tPtr->timerID)
		WMDeleteTimerHandler(tPtr->timerID);

	W_DestroyIC(tPtr->view);

	WMReleaseFont(tPtr->font);
	/*// use lostSelection() instead of WMDeleteSelectionHandler here? */
	WMDeleteSelectionHandler(tPtr->view, XA_PRIMARY, CurrentTime);
	WMRemoveNotificationObserver(tPtr);

	if (tPtr->text)
		wfree(tPtr->text);

	wfree(tPtr);
}
